using System.Diagnostics;
using System.Text;
using Framework.Core.Process;
using log4net;

namespace Framework.Core.Io
{
    internal class ProcessRunner : IProcessRunner
    {
        private readonly IProcessResultValidator processResultValidator;
        private static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessRunner));

        internal ProcessRunner(IProcessResultValidator processResultValidator)
        {
            this.processResultValidator = processResultValidator;
        }

        public ProcessRunner()
            : this(new ProcessResultValidator())
        {
        }

        public ProcessResult Start(ICommand command, int timeout = -1)
        {
            var standardOutput = new StringBuilder();
            var errorOutput = new StringBuilder();

            ProcessResult processResult;

            using (var p = new System.Diagnostics.Process())
            {
                p.StartInfo = new ProcessStartInfo
                              {
                                  Arguments = command.Arguments,
                                  CreateNoWindow = true,
                                  ErrorDialog = false,
                                  FileName = command.FileName,
                                  RedirectStandardError = true,
                                  RedirectStandardOutput = true,
                                  UseShellExecute = false,
                                  WindowStyle = ProcessWindowStyle.Hidden,
                              };

                p.OutputDataReceived += (sender, args) => WriteNotNullOrEmpty(args, standardOutput);
                p.ErrorDataReceived += (sender, args) => WriteNotNullOrEmpty(args, errorOutput);

                p.Start();

                p.BeginErrorReadLine();
                p.BeginOutputReadLine();

                var isTimeout = !p.WaitForExit(timeout);

                if (isTimeout)
                {
                    return new ProcessResult(-1, string.Empty, string.Empty, true);
                }

                var exitCode = p.ExitCode;
                var standard = standardOutput.ToString();
                var error = errorOutput.ToString();

                processResult = new ProcessResult(exitCode, standard, error, false);

                if (!processResultValidator.Validate(processResult))
                {
                    throw new ProcessResultException(processResult);
                }

                if (exitCode != 0)
                {
                    Logger.WarnFormat("process ({0} {1}) exit with code : {2}", command.Name, command.Arguments, processResult);
                }
            }
            return processResult;
        }

        private static void WriteNotNullOrEmpty(DataReceivedEventArgs args, StringBuilder standardOutput)
        {
            if (!string.IsNullOrWhiteSpace(args.Data))
            {
                standardOutput.AppendLine(args.Data);
            }
        }
    }
}